from rest_framework import permissions

from sysreptor.pentests.models import ProjectTypeScope, SourceEnum
from sysreptor.users.auth import forbidden_with_apitoken_auth
from sysreptor.utils import license
from sysreptor.utils.configuration import configuration


class IsAuthenticatedOrRetrieve(permissions.IsAuthenticated):
    def has_permission(self, request, view):
        if view.action and view.action.startswith('retrieve'):
          return True
        return super().has_permission(request, view)


class ProjectTypePermissions(permissions.BasePermission):
    # Read-only actions that can be performed by anyone having read permissions
    # and actions for private designs (permission check done in serializer to allow only private designs)
    public_actions = ['preview', 'export', 'copy', 'create', 'import_']
    # Write actions on a ProjectType instance
    private_actions = ['update', 'partial_update', 'destroy', 'lock', 'unlock', 'import_notes']
    # All other actions are only accessible for users with designer permissions

    def has_permission(self, request, view):
        if request.user.is_system_user:
            return False
        if request.user.is_admin or request.user.is_designer:
            return True
        if request.method in permissions.SAFE_METHODS or view.action in self.public_actions + self.private_actions:
            return True
        return False

    def has_object_permission(self, request, view, obj):
        if request.user.is_admin:
            return True
        if obj.scope == ProjectTypeScope.GLOBAL:
            return request.method in permissions.SAFE_METHODS or view.action in self.public_actions or \
                request.user.is_designer
        elif obj.scope == ProjectTypeScope.PRIVATE:
            return obj.linked_user == request.user and configuration.ENABLE_PRIVATE_DESIGNS
        elif obj.scope == ProjectTypeScope.PROJECT:
            if request.user not in set(map(lambda m: m.user, obj.linked_project.members.all())) and not request.user.is_project_admin:
                return False
            if request.method in permissions.SAFE_METHODS or view.action in self.public_actions:
                return True
            if obj.linked_project.readonly or (request.user.is_guest and not configuration.GUEST_USERS_CAN_EDIT_PROJECTS
                                               and not request.user.is_admin and not request.user.is_project_admin):
                return False
            return (view.action in self.private_actions and obj.source == SourceEnum.CUSTOMIZED) or \
                   (request.user.is_designer and obj.source == SourceEnum.CUSTOMIZED)
        return False


class ProjectTypeSubresourcePermissions(permissions.BasePermission):
    def has_permission(self, request, view):
        if request.user.is_system_user:
            return False
        if request.user.is_admin:
            return True
        project_type = view.get_project_type()
        if project_type.scope == ProjectTypeScope.GLOBAL:
            return request.method in permissions.SAFE_METHODS or \
                request.user.is_designer
        elif project_type.scope == ProjectTypeScope.PRIVATE:
            return project_type.linked_user == request.user and configuration.ENABLE_PRIVATE_DESIGNS
        elif project_type.scope == ProjectTypeScope.PROJECT:
            if request.user not in set(map(lambda m: m.user, project_type.linked_project.members.all())) and not request.user.is_project_admin:
                return False
            if request.method in permissions.SAFE_METHODS:
                return True
            if project_type.linked_project.readonly or (request.user.is_guest and not configuration.GUEST_USERS_CAN_EDIT_PROJECTS
                                                        and not request.user.is_admin and not request.user.is_project_admin):
                return False
            return project_type.source in [SourceEnum.CUSTOMIZED]
        return False


class IsTemplateEditorOrReadOnly(permissions.BasePermission):
    def has_permission(self, request, view):
        if request.user.is_system_user:
            return False
        return request.method in permissions.SAFE_METHODS or request.user.is_admin or request.user.is_template_editor


class ProjectPermissions(permissions.BasePermission):
    def has_permission(self, request, view):
        if request.user.is_system_user:
            return False

        if request.user.is_guest and not request.user.is_admin and not request.user.is_project_admin:
            if not configuration.GUEST_USERS_CAN_CREATE_PROJECTS and view.action in ['create', 'copy', 'import_']:
                return False
            elif not configuration.GUEST_USERS_CAN_IMPORT_PROJECTS and view.action in ['import_']:
                return False
            elif not configuration.GUEST_USERS_CAN_DELETE_PROJECTS and view.action in ['destroy']:
                return False
            elif not configuration.GUEST_USERS_CAN_UPDATE_PROJECT_SETTINGS and view.action in ['update', 'partial_update', 'readonly', 'customize_projecttype', 'archive', 'archive_check']:
                return False
            elif not configuration.GUEST_USERS_CAN_EDIT_PROJECTS and view.action in ['upload_image_or_file']:
                # Other edit permissions are checked in ProjectSubresourcePermissions
                return False

        if view.action in ['md2html'] and not license.ProfessionalLicenseRequired().has_permission(request, view):
            return False
        elif view.action in ['archive_check', 'archive']:
            if not license.ProfessionalLicenseRequired().has_permission(request, view):
                return False
            if not request.user.is_admin and not request.user.is_project_admin and not request.user.is_global_archiver and not configuration.PROJECT_MEMBERS_CAN_ARCHIVE_PROJECTS:
                return False
        return True

    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS or view.action in ['check', 'preview', 'generate', 'md2html', 'copy', 'export', 'export_all', 'readonly', 'archive', 'archive_check', 'destroy']:
            return True
        return not obj.readonly


class ProjectSubresourcePermissions(permissions.BasePermission):
    @classmethod
    def has_write_permissions(cls, project, user):
        if project.readonly:
            return False
        if user.is_guest and not configuration.GUEST_USERS_CAN_EDIT_PROJECTS and not user.is_admin and not user.is_project_admin:
            return False
        return True

    def has_permission(self, request, view):
        if request.user.is_system_user:
            return False
        if request.method in permissions.SAFE_METHODS or getattr(view, 'action', None) in ['export', 'export_all', 'export_pdf', 'export_pdf_multiple']:
            return True
        return self.has_write_permissions(project=view.get_project(), user=request.user)


class CommentPermissions(ProjectSubresourcePermissions):
    def has_object_permission(self, request, view, obj):
        if view.action in ['update', 'partial_update']:
            return request.user == obj.user or request.user.is_admin
        else:
            return True


class UserPublicKeyPermissions(permissions.BasePermission):
    def has_permission(self, request, view):
        forbidden_with_apitoken_auth(request)
        if view.kwargs.get('pentestuser_pk') == 'self':
            return True
        if request.user.is_admin or request.user.is_user_manager:
            return request.method in permissions.SAFE_METHODS
        return False


class ArchivedProjectKeyPartPermissions(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        if view.action in ['public_key_encrypted_data', 'decrypt']:
            return request.user.is_admin or request.user == obj.user
        return True


class ShareInfoPermissions(permissions.BasePermission):
    def has_permission(self, request, view):
        if request.method in permissions.SAFE_METHODS:
            return True
        elif configuration.DISABLE_SHARING:
            return False
        elif request.user.is_admin:
            return True
        elif request.user.is_guest and not configuration.GUEST_USERS_CAN_SHARE_NOTES:
            return False
        return True



class ShareInfoPublicPermissions(permissions.BasePermission):
    def has_permission(self, request, view):
        if configuration.DISABLE_SHARING:
            return False
        return True

    def has_object_permission(self, request, view, obj):
        if not obj.is_active:
            return False
        return True


class SharedProjectNotePublicPermissions(permissions.BasePermission):
    def has_permission(self, request, view):
        if configuration.DISABLE_SHARING:
            return False
        share_info = view.get_share_info()
        if not share_info.is_active:
            return False
        if share_info.password and str(share_info.id) not in request.session.get('authorized_shareids', []):
            return False
        if request.method in permissions.SAFE_METHODS:
            return True
        if share_info.permissions_write and not share_info.note.project.readonly:
            return True
        return False
